package compiler.parser;

import compiler.scanner.Scanner;
import compiler.scanner.Token;
import compiler.scanner.TokenId;
import compiler.syntax_tree.*;

public class ExpressionParser
    extends BaseParser
{
    public ExpressionParser(Scanner scanner, Token currentToken)
    {
        super(scanner);
        this.currentToken = currentToken;
    }

    public Expression parse()
    {
        return parseExpression();
    }

    private Expression parseExpression()
    {
        Expression expression = parseAndExpression();
        if (isOrExpressionOpt())
        {
            expression = parseOrExpressionOpt(expression);
        }
        return expression;
    }

    private Expression parseAndExpression()
    {
        Expression expression = parseComparisonExpression();
        if (isAndExpressionOpt())
        {
            expression = parseAndExpressionOpt(expression);
        }
        return expression;
    }

    private Expression parseOrExpressionOpt(Expression firstOperand)
    {
        Expression exprOpt = null;
        if (currentToken.getId() == TokenId.OR)
        {
            readToken();
            exprOpt = new Or(firstOperand, parseAndExpression());
            if (isOrExpressionOpt())
            {
                exprOpt = parseOrExpressionOpt(exprOpt);
            }
        }
        return exprOpt;
    }

    private Expression parseAndExpressionOpt(Expression firstOperand)
    {
        Expression exprOpt = null;
        if (currentToken.getId() == TokenId.AND)
        {
            readToken();
            exprOpt = new And(firstOperand, parseComparisonExpression());
            if (isAndExpressionOpt())
            {
                exprOpt = parseAndExpressionOpt(exprOpt);
            }
        }
        return exprOpt;
    }

    private Expression parseComparisonExpression()
    {
        Expression expression = parseArithmeticExpression();
        if (isComparisonExpressionOpt())
        {
            expression = parseComparisonExpressionOpt(expression);
        }
        return expression;
    }

    private Expression parseComparisonExpressionOpt(Expression firstOperand)
    {
        Expression exprOpt = null;
        if (currentToken.getId() == TokenId.LESS)
        {
            readToken();
            exprOpt = new LessThan(firstOperand, parseArithmeticExpression());
            if (isComparisonExpressionOpt())
            {
                exprOpt = parseComparisonExpressionOpt(exprOpt);
            }
        }
        return exprOpt;
    }

    private Expression parseArithmeticExpression()
    {
        Expression arithmeticExpression = parseTerm();
        if (isArithmeticExpressionOpt())
        {
            arithmeticExpression = parseArithmeticExpressionOpt(arithmeticExpression);
        }
        return arithmeticExpression;
    }

    private Expression parseArithmeticExpressionOpt(Expression firstOperand)
    {
        Expression arithmeticExpressionOpt = null;
        switch (currentToken.getId())
        {
            case PLUS:
                readToken();
                arithmeticExpressionOpt = new Plus(firstOperand, parseTerm());
                if (isArithmeticExpressionOpt())
                {
                    arithmeticExpressionOpt = parseArithmeticExpressionOpt(arithmeticExpressionOpt);
                }
                break;
            case MINUS:
                readToken();
                arithmeticExpressionOpt = new Minus(firstOperand, parseTerm());
                if (isArithmeticExpressionOpt())
                {
                    arithmeticExpressionOpt = parseArithmeticExpressionOpt(arithmeticExpressionOpt);
                }
                break;
        }
        return arithmeticExpressionOpt;
    }

    private Expression parseTerm()
    {
        Expression term = parseFactor();
        if (isTermOpt())
        {
            term = parseTermOpt(term);
        }
        return term;
    }

    private Expression parseTermOpt(Expression firstOperand)
    {
        Expression termOpt = null;
        switch (currentToken.getId())
        {
            case MUL:
                readToken();
                termOpt = new Multiply(firstOperand, parseFactor());
                if (isTermOpt())
                {
                    termOpt = parseTermOpt(termOpt);
                }
                break;
            case DIV:
                readToken();
                termOpt = new Div(firstOperand, parseFactor());
                if (isTermOpt())
                {
                    termOpt = parseTermOpt(termOpt);
                }
                break;
            case MOD:
                readToken();
                termOpt = new Mod(firstOperand, parseFactor());
                if (isTermOpt())
                {
                    termOpt = parseTermOpt(termOpt);
                }
                break;
        }
        return termOpt;
    }

    private Expression parseFactor()
    {
        Expression factor = null;
        switch (currentToken.getId())
        {
            case INTEGER_LITERAL:
                factor = new IntegerLiteral((Integer) currentToken.GetData());
                break;
            case IDENTIFIER:
                factor = ParseIdentifier();
                if (isTokenNext(TokenId.LEFT_SQUARE_BRACKET))
                {
                    readToken();
                    Expression expression = parseExpression();
                    readSpecificToken(TokenId.RIGHT_SQUARE_BRACKET);
                    factor = new ArrayLookup((Identifier) factor, expression);
                }
                break;
            case TRUE:
                factor = new True();
                break;
            case FALSE:
                factor = new False();
                break;
            case EXCLAMATION:
                readToken();
                factor = new Not(parseFactor());
                break;
            case LEFT_ROUND_BRACKET:
                readToken();
                factor = parseExpression();
                if (currentToken.getId() != TokenId.RIGHT_ROUND_BRACKET)
                {
                    handleError(") expected");
                }
                readToken();
                break;
            case NEW:
                readToken();
                readSpecificToken(TokenId.INT_TYPE);
                readSpecificToken(TokenId.LEFT_SQUARE_BRACKET);
                factor = new NewArray(parseExpression());
                readSpecificToken(TokenId.RIGHT_SQUARE_BRACKET);
                break;
            case MINUS:
                readToken();
                factor = parseFactor();
                if (factor instanceof IntegerLiteral)
                {
                    ((IntegerLiteral) factor).value = -((IntegerLiteral) factor).value;
                }
                else
                {
                    factor = new UnaryMinus(factor);
                }
                break;
            default:
                handleError("Expression expected");
                break;
        }
        return factor;
    }

    private boolean isComparisonExpressionOpt()
    {
        return isTokenNext(TokenId.LESS);
    }

    private boolean isArithmeticExpressionOpt()
    {
        return isTokenNext(TokenId.PLUS, TokenId.MINUS);
    }

    private boolean isAndExpressionOpt()
    {
        return isTokenNext(TokenId.AND);
    }

    private boolean isOrExpressionOpt()
    {
        return isTokenNext(TokenId.OR);
    }

    private boolean isTermOpt()
    {
        return isTokenNext(TokenId.MUL, TokenId.DIV, TokenId.MOD);
    }

}
